package pl.shenlon.poker.cards.evaluation;

import pl.shenlon.poker.cards.Card;
import pl.shenlon.poker.cards.CardRank;
import pl.shenlon.poker.cards.CardUtils;
import pl.shenlon.poker.cards.Hand;

public class HandRankEvaluator {
	public static final Hand EMPTY_HAND = new Hand(0);

	private static HandRankEvaluator instance;
	public static HandRankEvaluator getInstance() {
		if (instance == null) {
			instance = new HandRankEvaluator();
		}
		return instance;
	}

	protected HandRankEvaluator() {
	}

	public HandRank evaluateHand(Hand player) {
		return evaluateHand(player, EMPTY_HAND);
	}

	public HandRank evaluateHand(Hand player, Hand table) {

		Hand hand = CardUtils.joinHands(player, table);
		HandRank rank = HandRank.UNKNOWN;
		rank = isRoyalFlush(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isStraightFlush(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isFourOfAKind(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isFullHouse(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isFlush(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isStraight(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isThreeOfAKind(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isTwoPair(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		rank = isPair(hand);
		if (rank != HandRank.UNKNOWN) {
			return rank;
		}
		return HandRank.valueOf("HIGH_CARD_" + hand.getCard(0).getRank());
	}

	public HandRank isFlush(Hand hand) {
		if (hand.getCardsCount() < 5) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		int hearts = 0, clubs = 0, diamonds = 0, spades = 0;
		for (int i = 0; i < cards.length; i++) {
			int rank = cards[i].getRank().ordinal() + 1;
			switch (cards[i].getSuit()) {
				case CLUBS :
					clubs++;
					clubs = rank > clubs / 10 ? rank * 10 + clubs % 10 : clubs;
					break;
				case HEARTS :
					hearts++;
					hearts = rank > hearts / 10
							? rank * 10 + hearts % 10
							: hearts;
					break;
				case DIAMONDS :
					diamonds++;
					diamonds = rank > diamonds / 10
							? rank * 10 + diamonds % 10
							: diamonds;
					break;
				case SPADES :
					spades++;
					spades = rank > spades / 10
							? rank * 10 + spades % 10
							: spades;
					break;
			}
		}

		try {
			if (clubs % 10 == 5) {
				return HandRank.valueOf("FLUSH_"
						+ CardRank.values()[clubs / 10 - 1]);
			} else if (spades % 10 == 5) {
				return HandRank.valueOf("FLUSH_"
						+ CardRank.values()[spades / 10 - 1]);
			} else if (hearts % 10 == 5) {
				return HandRank.valueOf("FLUSH_"
						+ CardRank.values()[hearts / 10 - 1]);
			} else if (diamonds % 10 == 5) {
				return HandRank.valueOf("FLUSH_"
						+ CardRank.values()[diamonds / 10 - 1]);
			}
		} catch (IllegalArgumentException ex) {
			return HandRank.UNKNOWN;

			// TODO Fix this fix
		}
		//
		// for (int i = 0; i + 5 <= cards.length; i++) {
		// int max = i + 5;
		// int count = 1;
		// for (int j = i + 1; j < max; j++) {
		// if (cards[j].getSuit().equals(cards[i].getSuit())) {
		// count++;
		// } else {
		// break;
		// }
		// }
		// if (count == 5) {
		// try {
		// HandRank.valueOf("FLUSH_" + cards[i].getRank());
		// } catch (IllegalArgumentException ex) {
		// System.out.println(hand + " " + i);
		// }
		// return HandRank.valueOf("FLUSH_" + cards[i].getRank());
		// }
		// }
		return HandRank.UNKNOWN;
	}

	public HandRank isFourOfAKind(Hand hand) {
		if (hand.getCardsCount() < 4) {
			return HandRank.UNKNOWN;
		}
		// Map<CardRank, Integer> ranks = getGroupedCardRanks(hand);
		// return isFourOfAKind(ranks);
		Card[] cards = hand.getCards();
		for (int i = 0; i + 4 <= cards.length; i++) {
			int count = 1;
			int max = i + 4;
			for (int j = i + 1; j < max; j++) {
				if (cards[i].compareTo(cards[j]) == 0) {
					count++;
				} else {
					break;
				}
			}
			if (count == 4) {
				return HandRank.valueOf("FOUR_OF_A_KIND_" + cards[i].getRank());
			}
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isFullHouse(Hand hand) {
		if (hand.getCardsCount() < 5) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		CardRank pairRank = null, threeOfAKindRank = null;
		for (int i = 0; i + 2 < cards.length; i++) {
			int max = i + 3;
			int count = 1;
			for (int j = i + 1; j < max; j++) {
				if (cards[j].getRank().equals(cards[i].getRank())) {
					count++;
				} else {
					break;
				}
			}
			if (count == 3) {
				threeOfAKindRank = cards[i].getRank();
				break;
			}
		}

		if (threeOfAKindRank == null) {
			return HandRank.UNKNOWN;
		}
		for (int i = 0; i + 1 < cards.length; i++) {
			if (cards[i].getRank().equals(threeOfAKindRank)) {
				continue;
			}
			int max = i + 2;
			int count = 1;
			for (int j = i + 1; j < max; j++) {
				if (cards[j].getRank().equals(cards[i].getRank())) {
					count++;
				} else {
					break;
				}
			}
			if (count == 2) {
				pairRank = cards[i].getRank();
				break;
			}
		}
		if (pairRank != null && threeOfAKindRank != null) {
			return HandRank.valueOf("FULL_HOUSE_" + threeOfAKindRank);
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isPair(Hand hand) {
		Card[] cards = hand.getCards();
		for (int i = 0; i < cards.length - 1; i++) {
			if (cards[i + 1].getRank().equals(cards[i].getRank())) {
				return HandRank.valueOf("PAIR_" + cards[i].getRank());
			}
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isRoyalFlush(Hand hand) {
		if (hand.getCardsCount() < 5) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		for (int i = 0; i + 5 < cards.length; i++) {
			if (cards[i].getRank() != CardRank.ACE) {
				continue;
			}
			int difference = 0;
			for (int j = 1; j < 5; j++) {
				int delta = cards[i].hashCode() - cards[i + j].hashCode();
				if (delta != j) {
					difference = -1;
					break;
				}
				difference += delta;
			}
			if (difference == 10) {
				return HandRank.ROYAL_FLUSH;
			}
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isStraight(Hand hand) {
		if (hand.getCardsCount() < 5) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		for (int i = 0; i + 4 < cards.length; i++) {
			int max = i + 4;
			int difference = 0;
			for (int j = i + 1; j <= max; j++) {
				difference += cards[i].getRank().ordinal()
						- cards[j].getRank().ordinal();
			}
			if (difference == 10) {
				return HandRank.valueOf("STRAIGHT_" + cards[i].getRank());
			}
		}
		if (hand.containsRank(CardRank.FIVE)
				&& hand.containsRank(CardRank.FOUR)
				&& hand.containsRank(CardRank.THREE)
				&& hand.containsRank(CardRank.TWO)
				&& hand.containsRank(CardRank.ACE)) {
			return HandRank.STRAIGHT_FIVE;
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isStraightFlush(Hand hand) {
		if (hand.getCardsCount() < 5) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		for (int i = 0; i + 5 <= cards.length; i++) {
			int difference = 0;
			for (int j = 1; j < 5; j++) {
				int delta = cards[i].hashCode() - cards[i + j].hashCode();
				if (delta != j) {
					difference = -1;
					break;
				}
				difference += delta;
			}
			if (difference == 10) {
				return HandRank.valueOf("STRAIGHT_FLUSH_" + cards[i].getRank());
			}
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isThreeOfAKind(Hand hand) {
		if (hand.getCardsCount() < 3) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		for (int i = 0; i < cards.length - 2; i++) {
			int count = 1;
			for (int j = 1; j < 3; j++) {
				if (cards[i + j].getRank().equals(cards[i].getRank())) {
					count++;
				} else {
					break;
				}
			}
			if (count == 3) {
				return HandRank
						.valueOf("THREE_OF_A_KIND_" + cards[i].getRank());
			}
		}
		return HandRank.UNKNOWN;
	}

	public HandRank isTwoPair(Hand hand) {
		if (hand.getCardsCount() < 5) {
			return HandRank.UNKNOWN;
		}
		Card[] cards = hand.getCards();
		CardRank pairOneRank = null, pairTwoRank = null;
		for (int i = 0; i < cards.length - 1; i++) {
			if (cards[i].getRank().equals(cards[i + 1].getRank())) {
				if (pairOneRank == null) {
					pairOneRank = cards[i].getRank();
				} else {
					pairTwoRank = cards[i].getRank();
				}
			}
			if (pairOneRank != null && pairTwoRank != null) {
				break;
			}
		}
		if (pairOneRank != null && pairTwoRank != null) {
			for (int i = 0; i < cards.length; i++) {
				CardRank rank = cards[i].getRank();
				if (rank != pairOneRank && rank != pairTwoRank) {
					return HandRank.valueOf("TWO_PAIR_" + pairOneRank + "_"
							+ pairTwoRank + "_" + rank);
				}
			}
		}

		return HandRank.UNKNOWN;
	}
}
